package com.vhall.component.service.pendant.impl;

import cn.hutool.core.date.DatePattern;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.date.LocalDateTimeUtil;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.vhall.component.dao.pendant.PendantMapper;
import com.vhall.component.dao.pendant.PendantMappingMapper;
import com.vhall.component.dao.pendant.PendantOperateRecordMapper;
import com.vhall.component.dao.pendant.PendantStatsMapper;
import com.vhall.component.entity.page.vo.PageVO;
import com.vhall.component.entity.pendant.PendantEntity;
import com.vhall.component.entity.pendant.PendantOperateRecordEntity;
import com.vhall.component.entity.pendant.PendantStatsEntity;
import com.vhall.component.entity.pendant.vo.*;
import com.vhall.component.entity.room.dto.RoomsEntityDTO;
import com.vhall.component.framework.common.exception.BusinessException;
import com.vhall.component.plugin.common.exception.BizErrorCode;
import com.vhall.component.plugin.common.support.VssToken;
import com.vhall.component.plugin.common.support.VssTokenThreadLocal;
import com.vhall.component.plugin.common.utils.BeanCopyUtil;
import com.vhall.component.plugin.common.utils.RedisLocker;
import com.vhall.component.plugin.core.service.PaasService;
import com.vhall.component.plugin.core.service.event.PaasMessageReqDTO;
import com.vhall.component.service.pendant.PendantConsoleService;
import com.vhall.component.service.pendant.PendantService;
import com.vhall.component.service.pendant.assembler.PendantAssembler;
import com.vhall.component.service.pendant.common.PendantConstant;
import com.vhall.component.service.pendant.event.PendantClickReportEvent;
import com.vhall.component.service.room.RoomInnerService;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;

import static com.vhall.component.service.pendant.common.PendantConstant.ACTIVITY_PENDANT_EXPIRE_KEY;
import static com.vhall.component.service.pendant.common.PendantConstant.TYPE_FIXED;

/**
 * @author guoxin.wang
 * @date 2021-10-29
 */
@Slf4j
@Service
public class PendantServiceImpl implements PendantService {

    @Resource
    private PendantAssembler pendantAssembler;

    @Autowired
    private PendantMapper pendantMapper;

    @Autowired
    private PendantMappingMapper pendantMappingMapper;

    @Autowired
    private RoomInnerService roomInnerService;

    @Autowired
    private PendantStatsMapper pendantStatsMapper;

    @Autowired
    private PendantOperateRecordMapper pendantOperateRecordMapper;

    @Autowired
    private PaasService paasService;

    @Autowired
    private PendantConsoleService pendantConsoleService;

    @Autowired
    private RedisTemplate redisTemplate;

    @Autowired
    @Setter
    private ApplicationEventPublisher applicationEventPublisher;

    @Override
    public PageVO<PendantListPushPendantRspVO> getPushList(PendantListPushPendantReqVO reqVO) {
        reqVO.setDefault();
        PageVO<PendantListPushPendantRspVO> result;
        // 获取房间信息。
        RoomsEntityDTO roomInfo = roomInnerService.getRoomInfo(reqVO.getRoomId());
        if (roomInfo == null) {
            throw new BusinessException(BizErrorCode.EMPTY_ROOM);
        }
        // 3、执行查询操作。
        Page page = new Page<>(reqVO.getPage(), reqVO.getPageSize());
        Page<PendantEntity> pendantEntityPage = pendantMappingMapper.selectInfoPage(page, reqVO);
        List<PendantEntity> records = pendantEntityPage.getRecords();
        if (records == null || records.isEmpty()) {
            return new PageVO<>(reqVO.getPage(), reqVO.getPageSize(), 0, new ArrayList<>());
        }


        List<PendantListPushPendantRspVO> pendantListPushPendants = BeanCopyUtil.batchCopy(records, PendantListPushPendantRspVO::new);

        // 判断是否已经推屏
//        LocalDateTime now = LocalDateTime.now();
//        JSONObject jsonObject = judgePushFlag(roomInfo, now);
//        if (jsonObject != null) {
//            Integer id = jsonObject.getInteger("id");
//            List<PendantListPushPendantRspVO> collect = pendantListPushPendants.stream().filter(e -> e.getId().equals(id)).collect(Collectors.toList());
//            for (PendantListPushPendantRspVO pendantEntity : collect) {
//                pendantEntity.setPushScreenFlag(PushScreenFlagEnums.PUSH_SCREENING.getValue());
//            }
//        }
        result = new PageVO<>(reqVO.getPage(), reqVO.getPageSize(), (int) page.getTotal(), pendantListPushPendants);
        return result;
    }

    @Override
    public Boolean pushScreen(PendantPushScreenReqVO reqVO) {
        // 获取登陆用户消息
        VssToken accountInfo = VssTokenThreadLocal.getToken();
        PendantEntity pendant = pendantMapper.selectById(reqVO.getPendantId());
        if (pendant == null) {
            throw new BusinessException(BizErrorCode.COMP_PENDANT_EMPTY);
        }
        if (pendant.getType().equals(TYPE_FIXED)) {
            throw new BusinessException(BizErrorCode.COMP_PENDANT_TYPE_FAIL);
        }
        // 获取房间信息。
        RoomsEntityDTO roomInfo = roomInnerService.getRoomInfo(reqVO.getRoomId());
        if (roomInfo == null) {
            throw new BusinessException(BizErrorCode.EMPTY_ROOM);
        }

        LocalDateTime now = LocalDateTime.now();
//        if (judgePushFlag(roomInfo, now) != null) {
//            // 推屏中，取消推屏
//            PendantPushScreenReqVO pendantPushScreenReqVO = new PendantPushScreenReqVO();
//            pendantPushScreenReqVO.setPendantId(pendant.getId());
//            pendantPushScreenReqVO.setRoomId(roomInfo.getRoomId());
//            cancelPushScreen(pendantPushScreenReqVO);
//        }


        // 更新stat表
        Map<String, String> map = new HashMap<>(4);
        map.put("type", PendantConstant.MESSAGE_TYPE_PENDANT_PUSH_SCREEN);
        map.put("screen_second", reqVO.getScreenSecond().toString());
        updateStats(roomInfo.getIlId(), pendant, now, map, accountInfo.getThirdPartyUserId());
        // 发送消息
        JSONObject messageBody = new JSONObject();
        messageBody.put("type", PendantConstant.MESSAGE_TYPE_PENDANT_PUSH_SCREEN);
        messageBody.put("id", pendant.getId());
        messageBody.put("pendant_type", pendant.getType());
        messageBody.put("name", pendant.getName());
        messageBody.put("pendant_url", pendant.getPendantUrl());
        messageBody.put("pendant_weixin_url", pendant.getPendantWeixinUrl());
        messageBody.put("pic", pendant.getPic());
        messageBody.put("icon", pendant.getIcon());
        messageBody.put("screen_second", new BigDecimal(reqVO.getScreenSecond()).multiply(new BigDecimal("60")).toString());
        messageBody.put("push_time", LocalDateTimeUtil.format(now, DatePattern.NORM_DATETIME_PATTERN));

        PaasMessageReqDTO paasMessageReq = new PaasMessageReqDTO.Builder(roomInfo.getChannelId(), messageBody.toString()).initDefault().build();
        paasMessageReq.setType("service_custom");
        paasService.sendMessage(paasMessageReq);

        redisTemplate.opsForValue().set(ACTIVITY_PENDANT_EXPIRE_KEY + roomInfo.getRoomId(), JSONObject.toJSONString(messageBody), reqVO.getScreenSecond(), TimeUnit.MINUTES);
        return true;
    }

//    /**
//     * 判断推屏状态
//     * 返回值不为空，表示有进行中的推屏。并且是推屏内容
//     * null 表示没有
//     */
//    private JSONObject judgePushFlag(RoomsEntityDTO roomInfo, LocalDateTime now) {
//        // 判断是否已经推屏，如果有则取消推屏
//        Object o = redisTemplate.opsForValue().get(ACTIVITY_PENDANT_EXPIRE_KEY + roomInfo.getRoomId());
//        if (o != null) {
//            JSONObject pendantJson = JSON.parseObject(o.toString());
//            String pushTime = pendantJson.getString("push_time");
//            int screenSecond = pendantJson.getInteger("screen_second");
//            long toMillisTime = LocalDateTimeUtil.between(LocalDateTimeUtil.parse(pushTime, DatePattern.NORM_DATETIME_PATTERN), now).toMillis() / 1000;
//            log.info(MessageFormat.format("挂件推送时间：【{0}】 计划推送时长：【{1}】 距今时间：【{2}】秒", pushTime, screenSecond, toMillisTime));
//            if (toMillisTime < screenSecond) {
//                return pendantJson;
//            }
//        }
//        return null;
//    }

    @Override
    public Boolean clickReport(PendantClickReportReqVO reqVO) {
        // 获取登陆用户消息
        VssToken accountInfo = VssTokenThreadLocal.getToken();
        // 获取房间信息
        RoomsEntityDTO roomInfo = roomInnerService.getRoomInfo(reqVO.getRoomId());
        if (roomInfo == null) {
            throw new BusinessException(BizErrorCode.EMPTY_ROOM);
        }
        // 插入record表
        LocalDateTime now = LocalDateTime.now();
        PendantOperateRecordEntity param = new PendantOperateRecordEntity();
        param.setAccountId(accountInfo.getThirdPartyUserId());
        param.setPendantId(reqVO.getPendantId());
        param.setIlId(roomInfo.getIlId());
        param.setType(PendantConstant.OPERATE_TYPE_CLICK);
        param.setDate(DateUtil.format(now, DatePattern.NORM_DATE_PATTERN));
        param.setCreatedAt(now);
        param.setUpdatedAt(now);
        pendantOperateRecordMapper.insert(param);
        PendantEntity pendant = pendantMapper.selectById(reqVO.getPendantId());
        if (pendant == null) {
            throw new BusinessException(BizErrorCode.COMP_PENDANT_EMPTY);
        }
        Map<String, String> map = new HashMap<>(4);
        map.put("type", PendantConstant.TYPE_CLICK);
        return updateStats(roomInfo.getIlId(), pendant, now, map, accountInfo.getThirdPartyUserId());
    }

    private Boolean updateStats(Integer ilId, PendantEntity pendant, LocalDateTime now, Map<String, String> map, Integer accountId) {

        // 更新stat表
        LambdaQueryWrapper<PendantStatsEntity> query = Wrappers.lambdaQuery();
        query.eq(PendantStatsEntity::getIlId, ilId);
        query.eq(PendantStatsEntity::getPendantId, pendant.getId());
        String createDate = DateUtil.format(now, DatePattern.NORM_DATE_PATTERN);
        query.eq(PendantStatsEntity::getDate, createDate);
        PendantStatsEntity model = pendantStatsMapper.selectOne(query);
        if (model == null) {
            // 保存
            savePendantStats(ilId, pendant, now, map, accountId, createDate);
        } else {
            // 构建更新wrapper
            LambdaUpdateWrapper<PendantStatsEntity> update = assemblyUpdateWrapper(ilId, pendant, now, map, accountId, createDate, model);

            // 发送 挂件上报 事件，这里异步更新 pendant_stats 表
            applicationEventPublisher.publishEvent(new PendantClickReportEvent(this, update));
        }
        return true;
    }


    /**
     * 构建更新wrapper
     */
    private LambdaUpdateWrapper<PendantStatsEntity> assemblyUpdateWrapper(Integer ilId, PendantEntity pendant, LocalDateTime now, Map<String, String> map, Integer accountId, String createDate, PendantStatsEntity model) {
        LambdaUpdateWrapper<PendantStatsEntity> update = Wrappers.lambdaUpdate();
        update.eq(PendantStatsEntity::getIlId, ilId);
        update.eq(PendantStatsEntity::getPendantId, pendant.getId());
        update.eq(PendantStatsEntity::getPendantType, pendant.getType());
        update.eq(PendantStatsEntity::getDate, createDate);
        if (PendantConstant.TYPE_CLICK.equals(map.get("type"))) {
            update.setSql("pv_num = pv_num + 1");
            // 判断当前用户是否记录过。
            LambdaQueryWrapper<PendantOperateRecordEntity> queryStatsDetail = Wrappers.lambdaQuery();
            queryStatsDetail.eq(PendantOperateRecordEntity::getIlId, ilId);
            queryStatsDetail.eq(PendantOperateRecordEntity::getPendantId, pendant.getId());
            queryStatsDetail.eq(PendantOperateRecordEntity::getAccountId, accountId);
            // 按天控制uv，一天累计一次
            LocalDateTime startTime = DateUtil.parseLocalDateTime(createDate, DatePattern.NORM_DATE_PATTERN);
            queryStatsDetail.ge(PendantOperateRecordEntity::getCreatedAt, startTime);
            queryStatsDetail.lt(PendantOperateRecordEntity::getCreatedAt, startTime.plusDays(1));
            Integer count = pendantOperateRecordMapper.selectCount(queryStatsDetail);
            if (count <= 1) {
                update.setSql("uv_num = uv_num + 1");
            }
        } else {
            update.set(PendantStatsEntity::getDuration, model.getDuration() + Integer.parseInt(map.get("screen_second")));
            update.set(PendantStatsEntity::getPushScreenNum, model.getPushScreenNum() + 1);
        }
        update.set(PendantStatsEntity::getUpdatedAt, now);
        return update;
    }


    private Boolean savePendantStats(Integer ilId, PendantEntity pendant, LocalDateTime now, Map<String, String> map, Integer accountId, String createDate) {
        // 生成加锁key
        StringBuilder lockKey = getRedisLockKey(ilId, pendant, createDate);
        // 加锁
        if (!RedisLocker.tryLock(lockKey.toString(), PendantConstant.STATS_LOCK_VALUE, 10 * 1000L)) {
            try {
                Thread.sleep(50L);
            } catch (InterruptedException e) {
                log.error("Interrupted!", e);
                Thread.currentThread().interrupt();
            }
            return updateStats(ilId, pendant, now, map, accountId);
        }
        try {
            // 插入操作。
            PendantStatsEntity paramInsert = new PendantStatsEntity();
            paramInsert.setIlId(ilId);
            paramInsert.setPendantId(pendant.getId());
            paramInsert.setPendantType(pendant.getType());
            if (PendantConstant.TYPE_CLICK.equals(map.get("type"))) {
                paramInsert.setPvNum(1);
                paramInsert.setUvNum(1);
            } else {
                paramInsert.setDuration(Integer.valueOf(map.get("screen_second")));
            }
            paramInsert.setPushScreenNum(1);
            paramInsert.setCreatedAt(now);
            paramInsert.setUpdatedAt(now);

            paramInsert.setDate(createDate);
            pendantStatsMapper.insert(paramInsert);
        } finally {
            RedisLocker.unlock(lockKey.toString(), PendantConstant.STATS_LOCK_VALUE);
        }
        return null;
    }


    private StringBuilder getRedisLockKey(Integer ilId, PendantEntity pendant, String createDate) {
        StringBuilder sb = new StringBuilder();
        sb.append(PendantConstant.STATS_LOCK_KEY);
        sb.append("_");
        sb.append(ilId);
        sb.append("_");
        sb.append(pendant.getId());
        sb.append("_");
        sb.append(createDate);
        return sb;
    }

    @Override
    public PendantDefaultFixedRspVO getDefaultFixed(PendantDefaultFixedReqVO reqVO) {
        // 获取房间信息。
        RoomsEntityDTO roomInfo = roomInnerService.getRoomInfo(reqVO.getRoomId());
        if (roomInfo == null) {
            throw new BusinessException(BizErrorCode.EMPTY_ROOM);
        }
        PendantEntity defaultFixed = pendantConsoleService.getDefaultFixed(roomInfo);
        return pendantAssembler.entityToDefaultFixedRspVo(defaultFixed);
    }

    @Override
    public Boolean cancelPushScreen(PendantPushScreenReqVO reqVO) {

        // 获取登陆用户消息

        PendantEntity pendant = pendantMapper.selectById(reqVO.getPendantId());
        if (pendant == null) {
            throw new BusinessException(BizErrorCode.COMP_PENDANT_EMPTY);
        }
        // 获取房间信息。
        RoomsEntityDTO roomInfo = roomInnerService.getRoomInfo(reqVO.getRoomId());
        if (roomInfo == null) {
            throw new BusinessException(BizErrorCode.EMPTY_ROOM);
        }

        // 发送消息
        JSONObject messageBody = new JSONObject();
        messageBody.put("type", PendantConstant.MESSAGE_TYPE_CANCEL_PENDANT_PUSH_SCREEN);
        messageBody.put("id", pendant.getId());
        messageBody.put("pendant_type", pendant.getType());
        messageBody.put("name", pendant.getName());
        messageBody.put("pendant_url", pendant.getPendantUrl());
        messageBody.put("pendant_weixin_url", pendant.getPendantWeixinUrl());
        messageBody.put("pic", pendant.getPic());
        messageBody.put("icon", pendant.getIcon());

        PaasMessageReqDTO paasMessageReq = new PaasMessageReqDTO.Builder(roomInfo.getChannelId(), messageBody.toString()).initDefault().build();
        paasMessageReq.setType("service_custom");
        paasService.sendMessage(paasMessageReq);

        redisTemplate.delete(ACTIVITY_PENDANT_EXPIRE_KEY + roomInfo.getRoomId());
        return true;

    }

}